package xyz.hellothomas.sftp.client.autoconfigure;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.pool2.impl.AbandonedConfig;
import org.apache.commons.pool2.impl.DefaultEvictionPolicy;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.context.properties.bind.BindResult;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.ObjectUtils;
import xyz.hellothomas.sftp.client.core.SftpClient;
import xyz.hellothomas.sftp.client.core.SftpPool;
import xyz.hellothomas.sftp.client.core.SftpPooledObjectFactory;
import xyz.hellothomas.sftp.client.core.SftpTemplate;

import java.io.PrintWriter;

/**
 * @author Thomas
 * @date 2021/4/12 10:17
 * @descripton
 * @version 1.0
 */
@Slf4j
public class SftpTemplateRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware {

    private SftpClientProperties sftpClientProperties;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        if (sftpClientProperties.getClient() == null) {
            sftpClientProperties.getClients().stream().forEach(i -> registerSftpTemplate(registry, i));
        } else {
            registerSftpTemplate(registry, sftpClientProperties.getClient());
        }
    }

    private void registerSftpTemplate(BeanDefinitionRegistry registry,
                                      SftpClientProperties.SftpClientProperty sftpClientProperty) {
        SftpPool sftpPool = createSftpPool(sftpClientProperty);

        GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
        genericBeanDefinition.setBeanClass(SftpTemplate.class);
        ConstructorArgumentValues constructorArgumentValues = new ConstructorArgumentValues();
        constructorArgumentValues.addIndexedArgumentValue(0, sftpPool);
        genericBeanDefinition.setConstructorArgumentValues(constructorArgumentValues);

        registry.registerBeanDefinition(sftpClientProperty.getName(), genericBeanDefinition);
        log.info("{} registered, sftpClientProperty: {}", sftpClientProperty.getName(), sftpClientProperty);
    }

    @Override
    public void setEnvironment(Environment environment) {
        Iterable sources = ConfigurationPropertySources.get(environment);
        Binder binder = new Binder(sources);
        BindResult<SftpClientProperties> bindResult = binder.bind("sftp", SftpClientProperties.class);

        sftpClientProperties = bindResult.orElseThrow(() -> new RuntimeException("sftp客户端未配置"));
        if (ObjectUtils.isEmpty(sftpClientProperties.getClient()) &&
                ObjectUtils.isEmpty(sftpClientProperties.getClients())) {
            throw new RuntimeException("sftp客户端配置错误");
        }
        log.debug("SftpClientProperties:{}", sftpClientProperties);
    }

    public static SftpPool createSftpPool(SftpClientProperties.SftpClientProperty sftpClientProperty) {
        SftpPooledObjectFactory sftpPooledObjectFactory = createSftpFactory(sftpClientProperty);
        GenericObjectPoolConfig sftpPoolConfig = createSftpPoolConfig(sftpClientProperty);
        AbandonedConfig sftpAbandonedConfig = createSftpAbandonedConfig(sftpClientProperty);
        SftpPool sftpPool = new SftpPool(sftpPooledObjectFactory, sftpPoolConfig, sftpAbandonedConfig);

        return sftpPool;
    }

    private static SftpPooledObjectFactory createSftpFactory(SftpClientProperties.SftpClientProperty property) {
        return SftpPooledObjectFactory.builder()
                .host(property.getHost())
                .port(property.getPort())
                .username(property.getUsername())
                .password(property.getPassword())
                .connectTimeout(property.getConnectTimeout())
                .build();
    }

    private static GenericObjectPoolConfig<SftpClient> createSftpPoolConfig(SftpClientProperties.SftpClientProperty property) {
        SftpClientProperties.Pool pool = property.getPool();
        GenericObjectPoolConfig<SftpClient> config = new GenericObjectPoolConfig();
        config.setMaxTotal(pool.getMaxTotal());
        config.setMaxIdle(pool.getMaxIdle());
        config.setMinIdle(pool.getMinIdle());
        config.setLifo(pool.isLifo());
        config.setFairness(pool.isFairness());
        config.setMaxWaitMillis(pool.getMaxWaitMillis());
        config.setMinEvictableIdleTimeMillis(pool.getMinEvictableIdleTimeMillis());
        config.setEvictorShutdownTimeoutMillis(pool.getEvictorShutdownTimeoutMillis());
        config.setSoftMinEvictableIdleTimeMillis(pool.getSoftMinEvictableIdleTimeMillis());
        config.setNumTestsPerEvictionRun(pool.getNumTestsPerEvictionRun());
        config.setEvictionPolicy(null);
        config.setEvictionPolicyClassName(DefaultEvictionPolicy.class.getName());
        config.setTestOnCreate(pool.isTestOnCreate());
        config.setTestOnBorrow(pool.isTestOnBorrow());
        config.setTestOnReturn(pool.isTestOnReturn());
        config.setTestWhileIdle(pool.isTestWhileIdle());
        config.setTimeBetweenEvictionRunsMillis(pool.getTimeBetweenEvictionRunsMillis());
        config.setBlockWhenExhausted(pool.isBlockWhenExhausted());
        config.setJmxEnabled(pool.isJmxEnabled());
        config.setJmxNamePrefix(pool.getJmxNamePrefix());

        return config;
    }

    private static AbandonedConfig createSftpAbandonedConfig(SftpClientProperties.SftpClientProperty property) {
        SftpClientProperties.Abandoned abandoned = property.getAbandoned();
        AbandonedConfig config = new AbandonedConfig();
        config.setRemoveAbandonedOnBorrow(abandoned.isRemoveAbandonedOnBorrow());
        config.setRemoveAbandonedOnMaintenance(abandoned.isRemoveAbandonedOnMaintenance());
        config.setRemoveAbandonedTimeout(abandoned.getRemoveAbandonedTimeout());
        config.setLogAbandoned(abandoned.isLogAbandoned());
        config.setRequireFullStackTrace(abandoned.isRequireFullStackTrace());
        config.setLogWriter(new PrintWriter(System.out));
        config.setUseUsageTracking(abandoned.isUseUsageTracking());

        return config;
    }
}
